// SPDX-FileCopyrightText: SAP SE or an SAP affiliate company and Gardener contributors
//
// SPDX-License-Identifier: Apache-2.0

package garden

import (
	"context"
	"fmt"
	"net"
	"slices"
	"strings"
	"time"

	"github.com/Masterminds/semver/v3"
	"github.com/go-logr/logr"
	apierrors "k8s.io/apimachinery/pkg/api/errors"
	"k8s.io/apimachinery/pkg/api/resource"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/util/sets"
	"k8s.io/client-go/tools/record"
	"k8s.io/utils/clock"
	"k8s.io/utils/ptr"
	"sigs.k8s.io/controller-runtime/pkg/client"
	logf "sigs.k8s.io/controller-runtime/pkg/log"
	"sigs.k8s.io/controller-runtime/pkg/manager"
	"sigs.k8s.io/controller-runtime/pkg/reconcile"

	gardencorev1beta1 "github.com/gardener/gardener/pkg/apis/core/v1beta1"
	v1beta1constants "github.com/gardener/gardener/pkg/apis/core/v1beta1/constants"
	v1beta1helper "github.com/gardener/gardener/pkg/apis/core/v1beta1/helper"
	operatorv1alpha1 "github.com/gardener/gardener/pkg/apis/operator/v1alpha1"
	"github.com/gardener/gardener/pkg/apis/operator/v1alpha1/helper"
	"github.com/gardener/gardener/pkg/client/kubernetes"
	"github.com/gardener/gardener/pkg/client/kubernetes/clientmap"
	kubeapiserver "github.com/gardener/gardener/pkg/component/kubernetes/apiserver"
	"github.com/gardener/gardener/pkg/controllerutils"
	"github.com/gardener/gardener/pkg/features"
	operatorconfigv1alpha1 "github.com/gardener/gardener/pkg/operator/apis/config/v1alpha1"
	"github.com/gardener/gardener/pkg/utils/flow"
	"github.com/gardener/gardener/pkg/utils/gardener/tokenrequest"
	"github.com/gardener/gardener/pkg/utils/imagevector"
	secretsutils "github.com/gardener/gardener/pkg/utils/secrets"
	secretsmanager "github.com/gardener/gardener/pkg/utils/secrets/manager"
	versionutils "github.com/gardener/gardener/pkg/utils/version"
)

const (
	labelKeyOrigin     = "origin"
	labelValueOperator = "gardener-operator"
)

// Reconciler reconciles Gardens.
type Reconciler struct {
	Manager               manager.Manager
	RuntimeClientSet      kubernetes.Interface
	RuntimeVersion        *semver.Version
	Config                operatorconfigv1alpha1.OperatorConfiguration
	Clock                 clock.Clock
	Recorder              record.EventRecorder
	Identity              *gardencorev1beta1.Gardener
	ComponentImageVectors imagevector.ComponentImageVectors
	GardenNamespace       string
	// GardenClientMap is the ClientMap used to communicate with the virtual garden cluster. It should be set by AddToManager function but the field is still public for usage in tests.
	GardenClientMap clientmap.ClientMap
}

// Reconcile performs the main reconciliation logic.
func (r *Reconciler) Reconcile(ctx context.Context, request reconcile.Request) (reconcile.Result, error) {
	log := logf.FromContext(ctx)

	garden := &operatorv1alpha1.Garden{}
	if err := r.RuntimeClientSet.Client().Get(ctx, request.NamespacedName, garden); err != nil {
		if apierrors.IsNotFound(err) {
			log.V(1).Info("Object is gone, stop reconciling")
			return reconcile.Result{}, nil
		}
		return reconcile.Result{}, fmt.Errorf("error retrieving object from store: %w", err)
	}

	if err := r.ensureAtMostOneGardenExists(ctx); err != nil {
		log.Error(err, "Reconciliation prevented without automatic requeue")
		return reconcile.Result{}, nil
	}

	operationType := gardencorev1beta1.LastOperationTypeReconcile
	if garden.DeletionTimestamp != nil {
		operationType = gardencorev1beta1.LastOperationTypeDelete
	}

	if err := r.updateStatusOperationStart(ctx, garden, operationType); err != nil {
		return reconcile.Result{}, r.updateStatusOperationError(ctx, garden, err, operationType)
	}

	targetVersion, err := semver.NewVersion(garden.Spec.VirtualCluster.Kubernetes.Version)
	if err != nil {
		return reconcile.Result{}, fmt.Errorf("failed parsing version %q for virtual cluster: %w", garden.Spec.VirtualCluster.Kubernetes.Version, err)
	}

	secretsManager, err := secretsmanager.New(
		ctx,
		log.WithName("secretsmanager"),
		r.Clock,
		r.RuntimeClientSet.Client(),
		r.GardenNamespace,
		operatorv1alpha1.SecretManagerIdentityOperator,
		secretsmanager.Config{
			CASecretAutoRotation: true,
			SecretNamesToTimes:   lastSecretRotationStartTimes(garden),
		},
	)
	if err != nil {
		return reconcile.Result{}, r.updateStatusOperationError(ctx, garden, err, operationType)
	}

	if garden.DeletionTimestamp != nil {
		if result, err := r.delete(ctx, log, garden, secretsManager, targetVersion); err != nil {
			return result, r.updateStatusOperationError(ctx, garden, err, operationType)
		}
		return reconcile.Result{}, nil
	}

	if result, err := r.reconcile(ctx, log, garden, secretsManager, targetVersion); err != nil {
		return result, r.updateStatusOperationError(ctx, garden, err, operationType)
	} else if result.RequeueAfter > 0 {
		return result, nil
	}

	if err := r.updateStatusOperationSuccess(ctx, garden, operationType); err != nil {
		return reconcile.Result{}, err
	}

	// ETCD encryption key rotation requires 2 reconciliations to complete. In prepared phase
	// the encrypted data has been decrypted and re-encrypted with the new key, but the old key is still present.
	// The second reconciliation will remove the old key and set the phase to completed.
	if etcdEncryptionKeyRotationPhase := helper.GetETCDEncryptionKeyRotationPhase(garden.Status.Credentials); etcdEncryptionKeyRotationPhase == gardencorev1beta1.RotationPrepared &&
		helper.ShouldETCDEncryptionKeyRotationBeAutoCompleteAfterPrepared(garden.Status.Credentials) {
		return reconcile.Result{RequeueAfter: controllerutils.DefaultRequeueAfterDuration}, nil
	}

	return reconcile.Result{RequeueAfter: r.Config.Controllers.Garden.SyncPeriod.Duration}, nil
}

func (r *Reconciler) ensureAtMostOneGardenExists(ctx context.Context) error {
	gardenList := &metav1.PartialObjectMetadataList{}
	gardenList.SetGroupVersionKind(operatorv1alpha1.SchemeGroupVersion.WithKind("GardenList"))
	if err := r.RuntimeClientSet.Client().List(ctx, gardenList, client.Limit(2)); err != nil {
		return err
	}

	if len(gardenList.Items) <= 1 {
		return nil
	}

	return fmt.Errorf("there can be at most one operator.gardener.cloud/v1alpha1.Garden resource in the system at a time")
}

func (r *Reconciler) reportProgress(log logr.Logger, garden *operatorv1alpha1.Garden, reportProgress bool) flow.ProgressReporter {
	return flow.NewDelayingProgressReporter(clock.RealClock{}, func(ctx context.Context, stats *flow.Stats) {
		patch := client.MergeFrom(garden.DeepCopy())

		if garden.Status.LastOperation == nil {
			garden.Status.LastOperation = &gardencorev1beta1.LastOperation{}
		}
		if reportProgress {
			garden.Status.LastOperation.Progress = stats.ProgressPercent()
		}
		garden.Status.LastOperation.Description = flow.MakeDescription(stats)
		garden.Status.LastOperation.LastUpdateTime = metav1.NewTime(r.Clock.Now().UTC())

		if err := r.RuntimeClientSet.Client().Status().Patch(ctx, garden, patch); err != nil {
			log.Error(err, "Could not report reconciliation progress")
		}
	}, 5*time.Second)
}

func (r *Reconciler) updateStatusOperationStart(ctx context.Context, garden *operatorv1alpha1.Garden, operationType gardencorev1beta1.LastOperationType) error {
	var (
		now                = metav1.NewTime(r.Clock.Now().UTC())
		operations         = helper.GetGardenerOperations(garden.Annotations)
		filteredOperations = sets.New(operations...).UnsortedList()
		description        string
	)

	k8sLess134, err := versionutils.CompareVersions(garden.Spec.VirtualCluster.Kubernetes.Version, "<", "1.34")
	if err != nil {
		return fmt.Errorf("failed checking if Virtual Cluster k8s version is less than 1.34: %w", err)
	}

	switch operationType {
	case gardencorev1beta1.LastOperationTypeReconcile:
		description = "Reconciliation of Garden cluster initialized."
	case gardencorev1beta1.LastOperationTypeDelete:
		description = "Deletion of Garden cluster in progress."
	}

	garden.Status.LastOperation = &gardencorev1beta1.LastOperation{
		Type:           operationType,
		State:          gardencorev1beta1.LastOperationStateProcessing,
		Progress:       0,
		Description:    description,
		LastUpdateTime: now,
	}
	garden.Status.Gardener = r.Identity
	garden.Status.ObservedGeneration = garden.Generation

	for _, operation := range operations {
		switch operation {
		case v1beta1constants.OperationRotateCredentialsStart:
			filteredOperations = v1beta1helper.RemoveOperation(filteredOperations, v1beta1constants.OperationRotateCAStart,
				v1beta1constants.OperationRotateServiceAccountKeyStart,
				v1beta1constants.OperationRotateETCDEncryptionKey,
				v1beta1constants.OperationRotateETCDEncryptionKeyStart,
				v1beta1constants.OperationRotateObservabilityCredentials,
				v1beta1constants.ShootOperationRotateSSHKeypair,
			)
		case v1beta1constants.OperationRotateCredentialsComplete:
			filteredOperations = v1beta1helper.RemoveOperation(filteredOperations, v1beta1constants.OperationRotateCAComplete,
				v1beta1constants.OperationRotateServiceAccountKeyComplete,
				v1beta1constants.OperationRotateETCDEncryptionKeyComplete,
			)
		}
	}

	updatedOperations := slices.Clone(filteredOperations)

	for _, operation := range filteredOperations {
		switch operation {
		case v1beta1constants.GardenerOperationReconcile:
			updatedOperations = v1beta1helper.RemoveOperation(updatedOperations, operation)

		case v1beta1constants.OperationRotateCredentialsStart:
			updatedOperations = v1beta1helper.RemoveOperation(updatedOperations, operation)
			startRotationCA(garden, &now)
			startRotationServiceAccountKey(garden, &now)
			startRotationETCDEncryptionKey(garden, !k8sLess134, &now)
			startRotationObservability(garden, &now)
			startRotationWorkloadIdentityKey(garden, &now)
		case v1beta1constants.OperationRotateCredentialsComplete:
			updatedOperations = v1beta1helper.RemoveOperation(updatedOperations, operation)
			completeRotationCA(garden, &now)
			completeRotationServiceAccountKey(garden, &now)
			if k8sLess134 {
				completeRotationETCDEncryptionKey(garden, &now)
			}
			completeRotationWorkloadIdentityKey(garden, &now)

		case v1beta1constants.OperationRotateCAStart:
			updatedOperations = v1beta1helper.RemoveOperation(updatedOperations, operation)
			startRotationCA(garden, &now)
		case v1beta1constants.OperationRotateCAComplete:
			updatedOperations = v1beta1helper.RemoveOperation(updatedOperations, operation)
			completeRotationCA(garden, &now)

		case v1beta1constants.OperationRotateServiceAccountKeyStart:
			updatedOperations = v1beta1helper.RemoveOperation(updatedOperations, operation)
			startRotationServiceAccountKey(garden, &now)
		case v1beta1constants.OperationRotateServiceAccountKeyComplete:
			updatedOperations = v1beta1helper.RemoveOperation(updatedOperations, operation)
			completeRotationServiceAccountKey(garden, &now)

		case v1beta1constants.OperationRotateETCDEncryptionKey:
			updatedOperations = v1beta1helper.RemoveOperation(updatedOperations, operation)
			startRotationETCDEncryptionKey(garden, true, &now)
		case v1beta1constants.OperationRotateETCDEncryptionKeyStart:
			updatedOperations = v1beta1helper.RemoveOperation(updatedOperations, operation)
			startRotationETCDEncryptionKey(garden, false, &now)
		case v1beta1constants.OperationRotateETCDEncryptionKeyComplete:
			updatedOperations = v1beta1helper.RemoveOperation(updatedOperations, operation)
			completeRotationETCDEncryptionKey(garden, &now)

		case v1beta1constants.OperationRotateObservabilityCredentials:
			updatedOperations = v1beta1helper.RemoveOperation(updatedOperations, operation)
			startRotationObservability(garden, &now)

		case operatorv1alpha1.OperationRotateWorkloadIdentityKeyStart:
			updatedOperations = v1beta1helper.RemoveOperation(updatedOperations, operation)
			startRotationWorkloadIdentityKey(garden, &now)
		case operatorv1alpha1.OperationRotateWorkloadIdentityKeyComplete:
			updatedOperations = v1beta1helper.RemoveOperation(updatedOperations, operation)
			completeRotationWorkloadIdentityKey(garden, &now)
		}
	}

	// TODO(AleksandarSavchev): Remove the k8s version check in a future release after support for Kubernetes v1.33 is dropped.
	// It is added to forcefully complete the etcd encryption key rotation, since the annotation to complete the rotation
	// is forbidden for clusters with k8s >= v1.34.
	if helper.GetETCDEncryptionKeyRotationPhase(garden.Status.Credentials) == gardencorev1beta1.RotationPrepared &&
		(helper.ShouldETCDEncryptionKeyRotationBeAutoCompleteAfterPrepared(garden.Status.Credentials) || !k8sLess134) {
		completeRotationETCDEncryptionKey(garden, &now)
	}

	if err := r.RuntimeClientSet.Client().Status().Update(ctx, garden); err != nil {
		return err
	}

	if len(operations) != len(updatedOperations) {
		patch := client.MergeFrom(garden.DeepCopy())
		if len(updatedOperations) == 0 {
			delete(garden.Annotations, v1beta1constants.GardenerOperation)
		} else {
			garden.Annotations[v1beta1constants.GardenerOperation] = strings.Join(updatedOperations, ";")
		}
		return r.RuntimeClientSet.Client().Patch(ctx, garden, patch)
	}

	return nil
}

func (r *Reconciler) updateStatusOperationSuccess(ctx context.Context, garden *operatorv1alpha1.Garden, operationType gardencorev1beta1.LastOperationType) error {
	var (
		now         = metav1.NewTime(r.Clock.Now().UTC())
		description string
	)

	switch operationType {
	case gardencorev1beta1.LastOperationTypeReconcile:
		description = "Garden cluster has been successfully reconciled."
	case gardencorev1beta1.LastOperationTypeDelete:
		description = "Garden cluster has been successfully deleted."
	}

	garden.Status.LastOperation = &gardencorev1beta1.LastOperation{
		Type:           operationType,
		State:          gardencorev1beta1.LastOperationStateSucceeded,
		Progress:       100,
		Description:    description,
		LastUpdateTime: now,
	}

	switch helper.GetCARotationPhase(garden.Status.Credentials) {
	case gardencorev1beta1.RotationPreparing:
		helper.MutateCARotation(garden, func(rotation *gardencorev1beta1.CARotation) {
			rotation.Phase = gardencorev1beta1.RotationPrepared
			rotation.LastInitiationFinishedTime = &now
		})

	case gardencorev1beta1.RotationCompleting:
		helper.MutateCARotation(garden, func(rotation *gardencorev1beta1.CARotation) {
			rotation.Phase = gardencorev1beta1.RotationCompleted
			rotation.LastCompletionTime = &now
			rotation.LastInitiationFinishedTime = nil
			rotation.LastCompletionTriggeredTime = nil
		})
	}

	switch helper.GetServiceAccountKeyRotationPhase(garden.Status.Credentials) {
	case gardencorev1beta1.RotationPreparing:
		helper.MutateServiceAccountKeyRotation(garden, func(rotation *gardencorev1beta1.ServiceAccountKeyRotation) {
			rotation.Phase = gardencorev1beta1.RotationPrepared
			rotation.LastInitiationFinishedTime = &now
		})

	case gardencorev1beta1.RotationCompleting:
		helper.MutateServiceAccountKeyRotation(garden, func(rotation *gardencorev1beta1.ServiceAccountKeyRotation) {
			rotation.Phase = gardencorev1beta1.RotationCompleted
			rotation.LastCompletionTime = &now
			rotation.LastInitiationFinishedTime = nil
			rotation.LastCompletionTriggeredTime = nil
		})
	}

	switch helper.GetETCDEncryptionKeyRotationPhase(garden.Status.Credentials) {
	case gardencorev1beta1.RotationPreparing:
		helper.MutateETCDEncryptionKeyRotation(garden, func(rotation *gardencorev1beta1.ETCDEncryptionKeyRotation) {
			rotation.Phase = gardencorev1beta1.RotationPrepared
			rotation.LastInitiationFinishedTime = &now
		})

	case gardencorev1beta1.RotationCompleting:
		helper.MutateETCDEncryptionKeyRotation(garden, func(rotation *gardencorev1beta1.ETCDEncryptionKeyRotation) {
			rotation.Phase = gardencorev1beta1.RotationCompleted
			rotation.LastCompletionTime = &now
			rotation.LastInitiationFinishedTime = nil
			rotation.LastCompletionTriggeredTime = nil
			rotation.AutoCompleteAfterPrepared = nil
		})
	}

	if helper.IsObservabilityRotationInitiationTimeAfterLastCompletionTime(garden.Status.Credentials) {
		helper.MutateObservabilityRotation(garden, func(rotation *gardencorev1beta1.ObservabilityRotation) {
			rotation.LastCompletionTime = &now
		})
	}

	switch helper.GetWorkloadIdentityKeyRotationPhase(garden.Status.Credentials) {
	case gardencorev1beta1.RotationPreparing:
		helper.MutateWorkloadIdentityKeyRotation(garden, func(rotation *operatorv1alpha1.WorkloadIdentityKeyRotation) {
			rotation.Phase = gardencorev1beta1.RotationPrepared
			rotation.LastInitiationFinishedTime = &now
		})

	case gardencorev1beta1.RotationCompleting:
		helper.MutateWorkloadIdentityKeyRotation(garden, func(rotation *operatorv1alpha1.WorkloadIdentityKeyRotation) {
			rotation.Phase = gardencorev1beta1.RotationCompleted
			rotation.LastCompletionTime = &now
			rotation.LastInitiationFinishedTime = nil
			rotation.LastCompletionTriggeredTime = nil
		})
	}

	return r.RuntimeClientSet.Client().Status().Update(ctx, garden)
}

func (r *Reconciler) updateStatusOperationError(ctx context.Context, garden *operatorv1alpha1.Garden, err error, operationType gardencorev1beta1.LastOperationType) error {
	patch := client.MergeFrom(garden.DeepCopy())

	garden.Status.Gardener = r.Identity
	if garden.Status.LastOperation == nil {
		garden.Status.LastOperation = &gardencorev1beta1.LastOperation{}
	}
	garden.Status.LastOperation.Type = operationType
	garden.Status.LastOperation.State = gardencorev1beta1.LastOperationStateError
	garden.Status.LastOperation.Description = err.Error() + " Operation will be retried."
	garden.Status.LastOperation.LastUpdateTime = metav1.NewTime(r.Clock.Now().UTC())

	if err2 := r.RuntimeClientSet.Client().Status().Patch(ctx, garden, patch); err2 != nil {
		return fmt.Errorf("failed updating last operation to state 'Error' (due to %s): %w", err.Error(), err2)
	}

	return err
}

func (r *Reconciler) generateGenericTokenKubeconfig(ctx context.Context, garden *operatorv1alpha1.Garden, secretsManager secretsmanager.Interface) error {
	kubeAPIServerAddress := namePrefix + v1beta1constants.DeploymentNameKubeAPIServer
	if features.DefaultFeatureGate.Enabled(features.IstioTLSTermination) {
		kubeAPIServerAddress = v1beta1helper.GetAPIServerDomain(garden.Spec.VirtualCluster.DNS.Domains[0].Name)
	}

	genericTokenKubeconfigSecret, err := tokenrequest.GenerateGenericTokenKubeconfig(ctx, secretsManager, r.GardenNamespace, kubeAPIServerAddress)
	if err != nil {
		return err
	}

	if secretName := garden.Annotations[v1beta1constants.AnnotationKeyGenericTokenKubeconfigSecretName]; secretName != genericTokenKubeconfigSecret.Name {
		patch := client.MergeFrom(garden.DeepCopy())
		metav1.SetMetaDataAnnotation(&garden.ObjectMeta, v1beta1constants.AnnotationKeyGenericTokenKubeconfigSecretName, genericTokenKubeconfigSecret.Name)
		return r.RuntimeClientSet.Client().Patch(ctx, garden, patch)
	}
	return nil
}

func (r *Reconciler) cleanupGenericTokenKubeconfig(ctx context.Context, secretsManager secretsmanager.Interface) error {
	secret, exists := secretsManager.Get(v1beta1constants.SecretNameGenericTokenKubeconfig)
	if !exists {
		return nil
	}
	return client.IgnoreNotFound(r.RuntimeClientSet.Client().Delete(ctx, secret))
}

func (r *Reconciler) generateObservabilityIngressPassword(ctx context.Context, secretsManager secretsmanager.Interface) error {
	_, err := secretsManager.Generate(ctx, &secretsutils.BasicAuthSecretConfig{
		Name:           v1beta1constants.SecretNameObservabilityIngress,
		Format:         secretsutils.BasicAuthFormatNormal,
		Username:       "admin",
		PasswordLength: 32,
	}, secretsmanager.Persist(), secretsmanager.Rotate(secretsmanager.InPlace))
	return err
}

func startRotationCA(garden *operatorv1alpha1.Garden, now *metav1.Time) {
	helper.MutateCARotation(garden, func(rotation *gardencorev1beta1.CARotation) {
		rotation.Phase = gardencorev1beta1.RotationPreparing
		rotation.LastInitiationTime = now
		rotation.LastInitiationFinishedTime = nil
		rotation.LastCompletionTriggeredTime = nil
	})
}

func completeRotationCA(garden *operatorv1alpha1.Garden, now *metav1.Time) {
	helper.MutateCARotation(garden, func(rotation *gardencorev1beta1.CARotation) {
		rotation.Phase = gardencorev1beta1.RotationCompleting
		rotation.LastCompletionTriggeredTime = now
	})
}

func startRotationServiceAccountKey(garden *operatorv1alpha1.Garden, now *metav1.Time) {
	helper.MutateServiceAccountKeyRotation(garden, func(rotation *gardencorev1beta1.ServiceAccountKeyRotation) {
		rotation.Phase = gardencorev1beta1.RotationPreparing
		rotation.LastInitiationTime = now
		rotation.LastInitiationFinishedTime = nil
		rotation.LastCompletionTriggeredTime = nil
	})
}

func completeRotationServiceAccountKey(garden *operatorv1alpha1.Garden, now *metav1.Time) {
	helper.MutateServiceAccountKeyRotation(garden, func(rotation *gardencorev1beta1.ServiceAccountKeyRotation) {
		rotation.Phase = gardencorev1beta1.RotationCompleting
		rotation.LastCompletionTriggeredTime = now
	})
}

func startRotationETCDEncryptionKey(garden *operatorv1alpha1.Garden, autoCompleteAfterPrepared bool, now *metav1.Time) {
	helper.MutateETCDEncryptionKeyRotation(garden, func(rotation *gardencorev1beta1.ETCDEncryptionKeyRotation) {
		rotation.Phase = gardencorev1beta1.RotationPreparing
		rotation.LastInitiationTime = now
		rotation.LastInitiationFinishedTime = nil
		rotation.LastCompletionTriggeredTime = nil
		rotation.AutoCompleteAfterPrepared = ptr.To(autoCompleteAfterPrepared)
	})
}

func completeRotationETCDEncryptionKey(garden *operatorv1alpha1.Garden, now *metav1.Time) {
	helper.MutateETCDEncryptionKeyRotation(garden, func(rotation *gardencorev1beta1.ETCDEncryptionKeyRotation) {
		rotation.Phase = gardencorev1beta1.RotationCompleting
		rotation.LastCompletionTriggeredTime = now
	})
}

func startRotationObservability(garden *operatorv1alpha1.Garden, now *metav1.Time) {
	helper.MutateObservabilityRotation(garden, func(rotation *gardencorev1beta1.ObservabilityRotation) {
		rotation.LastInitiationTime = now
	})
}

func startRotationWorkloadIdentityKey(garden *operatorv1alpha1.Garden, now *metav1.Time) {
	helper.MutateWorkloadIdentityKeyRotation(garden, func(rotation *operatorv1alpha1.WorkloadIdentityKeyRotation) {
		rotation.Phase = gardencorev1beta1.RotationPreparing
		rotation.LastInitiationTime = now
		rotation.LastInitiationFinishedTime = nil
		rotation.LastCompletionTriggeredTime = nil
	})
}

func completeRotationWorkloadIdentityKey(garden *operatorv1alpha1.Garden, now *metav1.Time) {
	helper.MutateWorkloadIdentityKeyRotation(garden, func(rotation *operatorv1alpha1.WorkloadIdentityKeyRotation) {
		rotation.Phase = gardencorev1beta1.RotationCompleting
		rotation.LastCompletionTriggeredTime = now
	})
}

func caCertConfigurations() []secretsutils.ConfigInterface {
	return append([]secretsutils.ConfigInterface{
		&secretsutils.CertificateSecretConfig{Name: operatorv1alpha1.SecretNameCARuntime, CertType: secretsutils.CACert, Validity: ptr.To(30 * 24 * time.Hour)},
	}, nonAutoRotatedCACertConfigurations()...)
}

func nonAutoRotatedCACertConfigurations() []secretsutils.ConfigInterface {
	return []secretsutils.ConfigInterface{
		&secretsutils.CertificateSecretConfig{Name: v1beta1constants.SecretNameCAETCD, CommonName: "etcd", CertType: secretsutils.CACert},
		&secretsutils.CertificateSecretConfig{Name: v1beta1constants.SecretNameCAETCDPeer, CommonName: "etcd-peer", CertType: secretsutils.CACert},
		&secretsutils.CertificateSecretConfig{Name: v1beta1constants.SecretNameCACluster, CommonName: "kubernetes", CertType: secretsutils.CACert},
		&secretsutils.CertificateSecretConfig{Name: v1beta1constants.SecretNameCAClient, CommonName: "kubernetes-client", CertType: secretsutils.CACert},
		&secretsutils.CertificateSecretConfig{Name: v1beta1constants.SecretNameCAFrontProxy, CommonName: "front-proxy", CertType: secretsutils.CACert},
		&secretsutils.CertificateSecretConfig{Name: operatorv1alpha1.SecretNameCAGardener, CommonName: "gardener", CertType: secretsutils.CACert},
	}
}

func caCertGenerateOptionsFor(name string, rotationPhase gardencorev1beta1.CredentialsRotationPhase) []secretsmanager.GenerateOption {
	options := []secretsmanager.GenerateOption{secretsmanager.Rotate(secretsmanager.KeepOld)}

	if name == operatorv1alpha1.SecretNameCARuntime {
		options = append(options, secretsmanager.IgnoreOldSecretsAfter(24*time.Hour))
	} else if rotationPhase == gardencorev1beta1.RotationCompleting {
		options = append(options, secretsmanager.IgnoreOldSecrets())
	}

	return options
}

func lastSecretRotationStartTimes(garden *operatorv1alpha1.Garden) map[string]time.Time {
	rotation := make(map[string]time.Time)

	if gardenStatus := garden.Status; gardenStatus.Credentials != nil && gardenStatus.Credentials.Rotation != nil {
		if gardenStatus.Credentials.Rotation.CertificateAuthorities != nil && gardenStatus.Credentials.Rotation.CertificateAuthorities.LastInitiationTime != nil {
			for _, config := range nonAutoRotatedCACertConfigurations() {
				rotation[config.GetName()] = gardenStatus.Credentials.Rotation.CertificateAuthorities.LastInitiationTime.Time
			}
			rotation[kubeapiserver.SecretStaticTokenName] = gardenStatus.Credentials.Rotation.CertificateAuthorities.LastInitiationTime.Time
		}

		if gardenStatus.Credentials.Rotation.ServiceAccountKey != nil && gardenStatus.Credentials.Rotation.ServiceAccountKey.LastInitiationTime != nil {
			rotation[v1beta1constants.SecretNameServiceAccountKey] = gardenStatus.Credentials.Rotation.ServiceAccountKey.LastInitiationTime.Time
		}

		if gardenStatus.Credentials.Rotation.ETCDEncryptionKey != nil && gardenStatus.Credentials.Rotation.ETCDEncryptionKey.LastInitiationTime != nil {
			rotation[v1beta1constants.SecretNameETCDEncryptionKey] = gardenStatus.Credentials.Rotation.ETCDEncryptionKey.LastInitiationTime.Time
			rotation[v1beta1constants.SecretNameGardenerETCDEncryptionKey] = gardenStatus.Credentials.Rotation.ETCDEncryptionKey.LastInitiationTime.Time
		}

		if gardenStatus.Credentials.Rotation.Observability != nil && gardenStatus.Credentials.Rotation.Observability.LastInitiationTime != nil {
			rotation[v1beta1constants.SecretNameObservabilityIngress] = gardenStatus.Credentials.Rotation.Observability.LastInitiationTime.Time
		}

		if gardenStatus.Credentials.Rotation.WorkloadIdentityKey != nil && gardenStatus.Credentials.Rotation.WorkloadIdentityKey.LastInitiationTime != nil {
			rotation[operatorv1alpha1.SecretNameWorkloadIdentityKey] = gardenStatus.Credentials.Rotation.WorkloadIdentityKey.LastInitiationTime.Time
		}
	}

	return rotation
}

func vpaEnabled(settings *operatorv1alpha1.Settings) bool {
	if settings != nil && settings.VerticalPodAutoscaler != nil {
		return ptr.Deref(settings.VerticalPodAutoscaler.Enabled, false)
	}
	return false
}

func getValidVolumeSize(volume *operatorv1alpha1.Volume, size string) string {
	if volume == nil || volume.MinimumSize == nil {
		return size
	}

	quantity, err := resource.ParseQuantity(size)
	if err == nil && quantity.Cmp(*volume.MinimumSize) < 0 {
		return volume.MinimumSize.String()
	}

	return size
}

func valiEnabled(networking operatorv1alpha1.RuntimeNetworking) (bool, error) {
	for _, cidr := range networking.Pods {
		if _, ipNet, err := net.ParseCIDR(cidr); err != nil {
			return false, fmt.Errorf("failed parsing %q as CIDR: %w", networking.Pods, err)
		} else if ipNet.IP.To4() == nil {
			// If To4() returns nil, it's IPv6
			return false, nil
		}
	}

	return true, nil
}
